package com.quanxiaoha.xiaohashu.user.biz.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.quanxiaoha.framework.biz.context.holder.LoginUserContextHolder;
import com.quanxiaoha.framework.common.enums.StatusEnum;
import com.quanxiaoha.framework.common.exception.BizException;
import com.quanxiaoha.framework.common.response.Response;
import com.quanxiaoha.framework.common.util.JsonUtils;
import com.quanxiaoha.framework.common.util.ParamUtils;
import com.quanxiaoha.framework.redis.constant.RedisKeyConstants;
import com.quanxiaoha.xiaohashu.user.biz.constant.RoleConstants;
import com.quanxiaoha.xiaohashu.user.biz.domain.dataobject.RoleDO;
import com.quanxiaoha.xiaohashu.user.biz.domain.dataobject.UserDO;
import com.quanxiaoha.xiaohashu.user.biz.domain.dataobject.UserRoleDO;
import com.quanxiaoha.xiaohashu.user.biz.enums.ResponseCodeEnum;
import com.quanxiaoha.xiaohashu.user.biz.enums.SexEnum;
import com.quanxiaoha.xiaohashu.user.biz.mapper.UserDOMapper;
import com.quanxiaoha.xiaohashu.user.biz.model.vo.UpdateUserInfoReqVO;
import com.quanxiaoha.xiaohashu.user.biz.rpc.DistributedIdGeneratorRpcService;
import com.quanxiaoha.xiaohashu.user.biz.rpc.OssRpcService;
import com.quanxiaoha.xiaohashu.user.biz.service.RoleService;
import com.quanxiaoha.xiaohashu.user.biz.service.UserRoleService;
import com.quanxiaoha.xiaohashu.user.biz.service.UserService;
import com.quanxiaoha.xiaohashu.user.dto.req.FindUserByIdReqDTO;
import com.quanxiaoha.xiaohashu.user.dto.req.FindUserByPhoneReqDTO;
import com.quanxiaoha.xiaohashu.user.dto.req.RegisterUserReqDTO;
import com.quanxiaoha.xiaohashu.user.dto.req.UpdateUserPasswordReqDTO;
import com.quanxiaoha.xiaohashu.user.dto.resp.FindUserByIdRspDTO;
import com.quanxiaoha.xiaohashu.user.dto.resp.FindUserByPhoneRspDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author jingyanliu
 * @description 针对表【t_user(用户表)】的数据库操作Service实现
 * @createDate 2025-07-21 17:46:16
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserDOMapper, UserDO>
		implements UserService {
	private static final Cache<Long, FindUserByIdRspDTO> LOCAL_CACHE = Caffeine.newBuilder()
			.initialCapacity(10000) // 设置初始容量为 10000 个条目
			.maximumSize(10000) // 设置缓存的最大容量为 10000 个条目
			.expireAfterWrite(1, TimeUnit.HOURS) // 设置缓存条目在写入后 1 小时过期
			.build();
	@Resource
	private OssRpcService ossRpcService;
	@Resource
	private RedisTemplate<String, String> redisTemplate;
	@Resource
	private UserRoleService userRoleService;
	@Resource
	private RoleService roleService;
	@Resource
	private DistributedIdGeneratorRpcService distributedIdGeneratorRpcService;
	@Resource
	private ThreadPoolTaskExecutor taskExecutor;
	
	@Override
	public Response<?> updateUserInfo(UpdateUserInfoReqVO updateUserInfoReqVO) {
		UserDO userDO = new UserDO();
		// 设置当前需要更新的用户 ID
		userDO.setId(LoginUserContextHolder.getUserId());
		
		// 昵称
		String nickname = updateUserInfoReqVO.getNickname();
		if (StringUtils.isNotBlank(nickname)) {
			Preconditions.checkArgument(ParamUtils.checkNickname(nickname), ResponseCodeEnum.NICK_NAME_VALID_FAIL.getErrorMessage());
			userDO.setNickname(nickname);
		}
		
		// 小哈书号
		String xiaohashuId = updateUserInfoReqVO.getXiaohashuId();
		if (StringUtils.isNotBlank(xiaohashuId)) {
			Preconditions.checkArgument(ParamUtils.checkXiaohashuId(xiaohashuId), ResponseCodeEnum.XIAOHASHU_ID_VALID_FAIL.getErrorMessage());
			userDO.setXiaohashuId(xiaohashuId);
		}
		
		// 性别
		Integer sex = updateUserInfoReqVO.getSex();
		if (Objects.nonNull(sex)) {
			Preconditions.checkArgument(SexEnum.isValid(sex), ResponseCodeEnum.SEX_VALID_FAIL.getErrorMessage());
			userDO.setSex(sex);
		}
		
		// 生日
		LocalDate birthday = updateUserInfoReqVO.getBirthday();
		if (Objects.nonNull(birthday)) {
			userDO.setBirthday(birthday);
		}
		
		// 个人简介
		String introduction = updateUserInfoReqVO.getIntroduction();
		if (StringUtils.isNotBlank(introduction)) {
			Preconditions.checkArgument(ParamUtils.checkLength(introduction, 100), ResponseCodeEnum.INTRODUCTION_VALID_FAIL.getErrorMessage());
			userDO.setIntroduction(introduction);
		}
		
		// 头像
		MultipartFile avatarFile = updateUserInfoReqVO.getAvatar();
		if (Objects.nonNull(avatarFile)) {
			String avatar = ossRpcService.uploadFile(avatarFile);
			log.info("==> 调用 oss 服务成功，上传头像，url：{}", avatar);
			
			// 若上传头像失败，则抛出业务异常
			if (StringUtils.isBlank(avatar)) {
				throw new BizException(ResponseCodeEnum.UPLOAD_AVATAR_FAIL);
			}
			
			userDO.setAvatar(avatar);
		}
		
		// 背景图
		MultipartFile backgroundImgFile = updateUserInfoReqVO.getBackgroundImg();
		if (Objects.nonNull(backgroundImgFile)) {
			String backgroundImg = ossRpcService.uploadFile(backgroundImgFile);
			log.info("==> 调用 oss 服务成功，上传背景图，url：{}", backgroundImg);
			
			// 若上传背景图失败，则抛出业务异常
			if (StringUtils.isBlank(backgroundImg)) {
				throw new BizException(ResponseCodeEnum.UPLOAD_BACKGROUND_IMG_FAIL);
			}
			
			userDO.setBackgroundImg(backgroundImg);
		}
		
		updateById(userDO);
		return Response.success();
	}
	
	@Override
	@Transactional(rollbackFor = Exception.class)
	public Response<Long> register(RegisterUserReqDTO registerUserReqDTO) {
		String phone = registerUserReqDTO.getPhone();
		// 先判断该手机号是否已被注册
		UserDO userDO = lambdaQuery().eq(UserDO::getPhone, phone).one();
		log.info("==> 用户是否注册, phone: {}, userDO: {}", phone, JsonUtils.toJsonString(userDO));
		
		// 若已注册，则直接返回用户 ID
		if (Objects.nonNull(userDO)) {
			return Response.success(userDO.getId());
		}
		
		// 否则注册新用户
		// 获取全局自增的小哈书 ID
		String xiaohashuId = distributedIdGeneratorRpcService.getXiaohashuId();
		Long userId = Long.valueOf(distributedIdGeneratorRpcService.getUserId());
		userDO = UserDO.builder()
				.id(userId)
				.phone(phone)
				.xiaohashuId(String.valueOf(xiaohashuId)) // 自动生成小红书号 ID
				.nickname("小红薯" + xiaohashuId) // 自动生成昵称, 如：小红薯10000
				.status(StatusEnum.ENABLE.getValue()) // 状态为启用
				.build();
		
		// 添加入库
		save(userDO);
		
		// 给该用户分配一个默认角色
		UserRoleDO userRoleDO = UserRoleDO.builder()
				.userId(userId)
				.roleId(RoleConstants.COMMON_USER_ROLE_ID)
				.build();
		userRoleService.save(userRoleDO);
		RoleDO roleDO = roleService.getById(RoleConstants.COMMON_USER_ROLE_ID);
		
		// 将该用户的角色 ID 存入 Redis 中
		List<String> roles = new ArrayList<>(5);
		roles.add(roleDO.getRoleKey());
		
		String userRolesKey = RedisKeyConstants.buildUserRoleKey(userId);
		redisTemplate.opsForValue().set(userRolesKey, JsonUtils.toJsonString(roles));
		
		return Response.success(userId);
	}
	
	@Override
	public Response<FindUserByPhoneRspDTO> findByPhone(FindUserByPhoneReqDTO findUserByPhoneReqDTO) {
		String phone = findUserByPhoneReqDTO.getPhone();
		
		// 根据手机号查询用户信息
		UserDO userDO = lambdaQuery().eq(UserDO::getPhone, phone).one();
		
		// 判空
		if (Objects.isNull(userDO)) {
			throw new BizException(ResponseCodeEnum.USER_NOT_FOUND);
		}
		
		// 构建返参
		FindUserByPhoneRspDTO findUserByPhoneRspDTO = FindUserByPhoneRspDTO.builder()
				.id(userDO.getId())
				.password(userDO.getPassword())
				.build();
		
		return Response.success(findUserByPhoneRspDTO);
	}
	
	@Override
	public Response<?> updatePassword(UpdateUserPasswordReqDTO updateUserPasswordReqDTO) {
		// 获取当前请求对应的用户 ID
		Long userId = LoginUserContextHolder.getUserId();
		this.lambdaUpdate().set(UserDO::getPassword, updateUserPasswordReqDTO.getEncodePassword())
				.eq(UserDO::getId, userId)
				.eq(UserDO::getStatus, 0).update();
		return Response.success();
	}
	
	@Override
	public Response<FindUserByIdRspDTO> findById(FindUserByIdReqDTO findUserByIdReqDTO) {
		Long userId = findUserByIdReqDTO.getId();
		// 先从本地缓存中查询
		FindUserByIdRspDTO findUserByIdRspDTOLocalCache = LOCAL_CACHE.getIfPresent(userId);
		if (Objects.nonNull(findUserByIdRspDTOLocalCache)) {
			log.info("==> 命中了本地缓存；{}", findUserByIdRspDTOLocalCache);
			return Response.success(findUserByIdRspDTOLocalCache);
		}
		String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(userId);
		String userInfoRedisValue = redisTemplate.opsForValue().get(userInfoRedisKey);
		
		// 若 Redis 缓存中存在该用户信息
		if (StringUtils.isNotBlank(userInfoRedisValue)) {
			// 将存储的 Json 字符串转换成对象，并返回
			FindUserByIdRspDTO findUserByIdRspDTO = JsonUtils.parseObject(userInfoRedisValue, FindUserByIdRspDTO.class);
			log.info("==> 命中了redis缓存；{}", findUserByIdRspDTO);
			
			// 异步线程中将用户信息存入本地缓存
			taskExecutor.submit(() -> {
				if (Objects.nonNull(findUserByIdRspDTO)) {
					// 写入本地缓存
					LOCAL_CACHE.put(userId, findUserByIdRspDTO);
					log.info("==> 写入本地缓存；{}", findUserByIdRspDTO);
				}
			});
			return Response.success(findUserByIdRspDTO);
		}
		
		// 否则, 从数据库中查询
		UserDO userDO = this.lambdaQuery().eq(UserDO::getId, userId).eq(UserDO::getStatus, 0).one();
		
		// 判空
		if (Objects.isNull(userDO)) {
			taskExecutor.execute(() -> {
				// 防止缓存穿透，将空数据存入 Redis 缓存 (过期时间不宜设置过长)
				// 保底1分钟 + 随机秒数
				long expireSeconds = 60 + RandomUtil.randomInt(60);
				redisTemplate.opsForValue().set(userInfoRedisKey, "null", expireSeconds, TimeUnit.SECONDS);
			});
			throw new BizException(ResponseCodeEnum.USER_NOT_FOUND);
		}
		
		// 构建返参
		FindUserByIdRspDTO findUserByIdRspDTO = FindUserByIdRspDTO.builder()
				.id(userDO.getId())
				.nickName(userDO.getNickname())
				.avatar(userDO.getAvatar())
				.build();
		
		// 异步将用户信息存入 Redis 缓存，提升响应速度
		taskExecutor.submit(() -> {
			// 过期时间（保底1天 + 随机秒数，将缓存过期时间打散，防止同一时间大量缓存失效，导致数据库压力太大）
			long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
			redisTemplate.opsForValue()
					.set(userInfoRedisKey, JsonUtils.toJsonString(findUserByIdRspDTO), expireSeconds, TimeUnit.SECONDS);
			log.info("==> 存入redis缓存；{}", findUserByIdRspDTO);
		});
		
		return Response.success(findUserByIdRspDTO);
	}
}
